/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: ps2 mouse irq driver
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-06-20     JasonHu           Init
 */

#define NX_LOG_NAME "ps2mouse irq"
#define NX_LOG_LEVEL NX_LOG_INFO
#include <base/log.h>

#ifdef CONFIG_NX_DRIVER_PS2_MOUSE_IRQ

#include <base/page.h>
#include <base/driver.h>
#include <base/malloc.h>
#include <base/debug.h>
#include <base/delay_irq.h>
#include <base/fifo.h>
#include <base/semaphore.h>
#include <base/thread.h>
#include <base/memory.h>

#include <drvfw/irq.h>

#include <interrupt.h>
#include <io.h>

#define DRV_NAME "ps2 mouse irq driver"
#define DRV_VERSION "0.1"

#define DEV_NAME "ps2mouse_irq"

#define I8042_BUFFER 0x60
#define I8042_STATUS 0x64
#define I8042_ACK 0xFA
#define I8042_BUFFER_FULL 0x01
#define I8042_WHICH_BUFFER 0x20
#define I8042_MOUSE_BUFFER 0x20
#define I8042_KEYBOARD_BUFFER 0x00

#define MOUSE_FIFO_MAX_SZ  128

typedef struct HalDeviceExtension
{
    char irq;           /* irq号 */
    NX_Fifo * fifo;
    NX_Semaphore fifoSem;
    NX_Bool opened; /* irq driver opened */
} HalDeviceExtension;

NX_PRIVATE HalDeviceExtension halMouseExtension;

NX_PRIVATE NX_Error MouseIrqOpen(struct NX_Device *device, NX_U32 flags)
{
    HalDeviceExtension * devext = (HalDeviceExtension *)device->extension;

    NX_LOG_D("MOUSE IRQ OPEN");
    NX_FifoReset(devext->fifo);
    NX_SemaphoreInit(&devext->fifoSem, 0);
    
    devext->opened = NX_True;
    
    return NX_EOK;
}

NX_PRIVATE NX_Error MouseIrqClose(struct NX_Device *device)
{
    HalDeviceExtension * devext = (HalDeviceExtension *)device->extension;
    NX_LOG_D("MOUSE IRQ CLOSE");
    /* disable irq */
    devext->opened = NX_False;
    NX_SemaphoreInit(&devext->fifoSem, 0);
    NX_FifoReset(devext->fifo);
    NX_IRQ_Mask(devext->irq);

    return NX_EOK;
}

/* read irq data */
NX_PRIVATE NX_Error MouseIrqRead(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    HalDeviceExtension * ext = device->extension;

    if (!len || len != 1)
    {
        return NX_EINVAL;
    }

    if (ext->opened == NX_False)
    {
        NX_LOG_W("mouse irq not open\n");
        if (outLen)
        {
            *outLen = 0;
        }
        return NX_ENORES;
    }

    NX_U8 scan_code = 0;
    if (NX_SemaphoreWait(&ext->fifoSem) != NX_EOK)
    {
        NX_LOG_D("mouse irq wait intr\n");
        if (outLen)
        {
            *outLen = 0;
        }
        return NX_EINTR;
    }

    if (!NX_FifoRead(ext->fifo, &scan_code, 1))
    {
        NX_LOG_W("mouse irq fifo no data\n");
        if (outLen)
        {
            *outLen = 0;
        }
        return NX_ENORES;
    }

    NX_MemCopy(buf, &scan_code, 1);

    if (outLen)
    {
        *outLen = len;
    }
    return NX_EOK;
}

NX_PRIVATE NX_Error MouseIrqControl(struct NX_Device *device, NX_U32 cmd, void *arg)
{
    HalDeviceExtension * ext = device->extension;

    switch (cmd)
    {
    case NX_IRQ_CMD_MASK:
        NX_IRQ_Mask(ext->irq);
        break;
    case NX_IRQ_CMD_UNMASK:
        NX_IRQ_Unmask(ext->irq);
        break;

    default:
        break;
    }
    return NX_EOK;
}

NX_PRIVATE NX_DriverOps mouseIrqDriverOps = {
    .read       = MouseIrqRead,
    .open       = MouseIrqOpen,
    .close      = MouseIrqClose,
    .control    = MouseIrqControl,
};

NX_PRIVATE NX_Error MouseIrqHandler(NX_IRQ_Number irqno, void *arg)
{
    HalDeviceExtension * devext = (HalDeviceExtension *)arg;

    while (1)
    {
        NX_U8 status = IO_In8(I8042_STATUS);
        if (!(((status & I8042_WHICH_BUFFER) == I8042_MOUSE_BUFFER) && (status & I8042_BUFFER_FULL)))
        {
            break;
        }

        NX_U8 data = IO_In8(I8042_BUFFER);
        if (devext->opened)
        {
            NX_FifoWrite(devext->fifo, &data, 1);
            NX_SemaphoreSignal(&devext->fifoSem);
        }
    }
    return NX_EOK;
}

NX_PRIVATE void MouseIrqDriverInit(void)
{
    NX_Device *device;
    HalDeviceExtension * devext;

    NX_Driver *driver = NX_DriverCreate(DRV_NAME, NX_DEVICE_TYPE_IRQ, 0, &mouseIrqDriverOps);
    if (driver == NX_NULL)
    {
        NX_LOG_E("create driver failed!");
        return;
    }

    if (NX_DriverAttachDevice(driver, DEV_NAME, &device) != NX_EOK)
    {
        NX_LOG_E("attach device %s failed!", DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }

    devext = &halMouseExtension;
    device->extension = devext;

    devext->irq = IRQ_MOUSE;

    devext->opened = NX_False;
    devext->fifo = NX_FifoCreate(MOUSE_FIFO_MAX_SZ);
    if (devext->fifo == NX_NULL)
    {
        NX_LOG_E("create %s fifo failed!", DEV_NAME);
        
        NX_DriverDetachDevice(driver, DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
    NX_SemaphoreInit(&devext->fifoSem, 0);

    NX_ASSERT(NX_IRQ_Bind(devext->irq, MouseIrqHandler, devext, "ps2mouse", 0) == NX_EOK);
    
    if (NX_DriverRegister(driver) != NX_EOK)
    {
        NX_LOG_E("register driver %s failed!", DRV_NAME);

        NX_ASSERT(NX_IRQ_Unbind(devext->irq, NX_NULL) == NX_EOK);

        NX_FifoDestroy(devext->fifo);

        NX_DriverDetachDevice(driver, DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
}

NX_PRIVATE void MouseIrqDriverExit(void)
{
    NX_DriverCleanup(DRV_NAME);
}

NX_DRV_INIT(MouseIrqDriverInit);
NX_DRV_EXIT(MouseIrqDriverExit);

#endif /* CONFIG_NX_DRIVER_PS2_MOUSE_IRQ */
